﻿/******************************************************************************
* SunnyUI.FrameDecoder 开源TCP、串口数据解码库。
* CopyRight (C) 2022-2023 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@qq.com
*
* Blog:   https://www.cnblogs.com/yhuse
* Gitee:  https://gitee.com/yhuse/SunnyUI.FrameDecoder
*
* SunnyUI.FrameDecoder.dll can be used for free under the MIT license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: HeaderTagLengthValueTailFrameDecoder.cs
* 文件说明: 数据头部、标签、数据长度、数据、数据尾部 - 数据帧解码器
* 当前版本: V1.0
* 创建日期: 2022-11-01
*
* 2020-01-01: V1.0.0 增加文件说明
******************************************************************************/

using System;
using System.Threading.Tasks;

namespace Sunny.FrameDecoder
{
    /// <summary>
    /// 数据头部、标签、数据长度、数据、数据尾部 - 数据帧解码器
    /// </summary>
    public class HeaderTagLengthValueTailFrameDecoder : BaseByteFrameDecoder
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="headers">数据头部</param>
        /// <param name="tails">数据尾部</param>
        /// <param name="tagLength">标签长度</param>
        /// <param name="lengthType">数据长度类型</param>
        /// <param name="valueLengthIsLittleEndian">数据长度字节顺序</param>
        /// <param name="isFullLength">数据长度是否包含数据头部、标签、数据长度、数据尾部的长度，false为仅数据长度</param>
        /// <param name="maxFrameLength">最大数据长度，仅判断数据长度</param>
        public HeaderTagLengthValueTailFrameDecoder(byte[] headers, byte[] tails, int tagLength, LengthType lengthType,
            bool valueLengthIsLittleEndian = true, bool isFullLength = true, int maxFrameLength = 0)
        {
            if (headers == null || headers.Length == 0)
            {
                throw new ArgumentNullException(nameof(headers), $"Headers cannot be empty.");
            }

            if (tails == null || tails.Length == 0)
            {
                throw new ArgumentNullException(nameof(tails), $"Tails cannot be empty.");
            }

            if (tagLength < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(tagLength), $"Tag length must be greater than or equal to zero.");
            }

            _headerLength = headers.Length;
            _tailLength = tails.Length;

            Headers = headers;
            Tails = tails;

            MaxFrameLength = maxFrameLength;

            TagLength = tagLength;
            LengthType = lengthType;
            IsFullLength = isFullLength;
            MaxFrameLength = maxFrameLength;
            _valueLengthIsLittleEndian = valueLengthIsLittleEndian;

            FrameExceptDataLength = _headerLength + TagLength + LengthType.Size() + _tailLength;
        }

        ///<inheritdoc/>
        public override BaseByteFrameDecoder Clone()
        {
            return new HeaderTagLengthValueTailFrameDecoder(Headers, Tails, TagLength, LengthType,
                _valueLengthIsLittleEndian, IsFullLength, MaxFrameLength);
        }

        private readonly int _headerLength;
        private readonly int _tailLength;

        /// <summary>
        /// 数据头部
        /// </summary>
        public byte[] Headers { get; }

        /// <summary>
        /// 数据尾部
        /// </summary>
        public byte[] Tails { get; }

        private readonly bool _valueLengthIsLittleEndian;

        /// <summary>
        /// 标签长度
        /// </summary>
        public int TagLength { get; }

        /// <summary>
        /// 数据长度类型
        /// </summary>
        public LengthType LengthType { get; }

        /// <summary>
        /// 
        /// </summary>
        public bool IsFullLength { get; }

        /// <summary>
        /// 解码函数
        /// </summary>
        protected override void Decoding()
        {
            if (Cache.WrittenCount < FrameExceptDataLength) return;

            int index = Cache.WrittenSpan.IndexOf(Headers);
            if (index > 0) Cache.Remove(index);
            if (Cache.WrittenCount < FrameExceptDataLength) return;

            byte[] tag = Cache.WrittenSpan.Slice(_headerLength, TagLength).ToArray();
            int len = Cache.WrittenSpan.GetLengthValue(_headerLength + TagLength, LengthType.Size(), _valueLengthIsLittleEndian);
            int dataLen = IsFullLength ? len - FrameExceptDataLength : len;

            if (dataLen < 0)
            {
                Cache.Remove(Headers.Length);
                return;
            }

            while (index >= 0)
            {
                if (MinFrameLength > 0 && dataLen < MinFrameLength)
                {
                    DecoderError("The data length is less than the minimum length", Cache.WrittenSpan);
                    Reset();
                    break;
                }

                if (MaxFrameLength > 0 && MaxFrameLength > MinFrameLength && dataLen > MaxFrameLength)
                {
                    DecoderError("The data length is greater than the maximum length.", Cache.WrittenSpan);
                    Reset();
                    break;
                }

                int allLen = dataLen + FrameExceptDataLength;
                if (allLen <= Cache.WrittenCount)
                {
                    if (Cache.WrittenSpan.Slice(_headerLength + TagLength + LengthType.Size() + dataLen, _tailLength).SequenceEqual(Tails.AsSpan()))
                    {
                        LastDecodedTime = DateTime.Now;
                        ByteFrameEventArgs e = new(this, ByteFrameParams.TagLengthParams(tag, len),
                            _headerLength + TagLength + LengthType.Size(), dataLen, GetResult(0, allLen));
                        InvokeOnDecoder(e);
                    }
                    else
                    {
                        DecoderError("Tail data verification failed.", Cache.WrittenSpan);
                        Reset();
                        break;
                    }
                }
                else
                {
                    break;
                }

                Cache.Remove(allLen);
                if (Cache.WrittenCount < FrameExceptDataLength) break;

                index = Cache.WrittenSpan.IndexOf(Headers);
                if (index > 0) Cache.Remove(index);
                if (Cache.WrittenCount < FrameExceptDataLength) break;

                tag = Cache.WrittenSpan.Slice(_headerLength, TagLength).ToArray();
                len = Cache.WrittenSpan.GetLengthValue(_headerLength + TagLength, LengthType.Size(), _valueLengthIsLittleEndian);
                dataLen = IsFullLength ? len - FrameExceptDataLength : len;
            }
        }

        /// <summary>
        /// 解码函数
        /// </summary>
        protected override async Task DecodingAsync()
        {
            if (Cache.WrittenCount < FrameExceptDataLength) return;

            int index = Cache.WrittenSpan.IndexOf(Headers);
            if (index > 0) Cache.Remove(index);
            if (Cache.WrittenCount < FrameExceptDataLength) return;

            byte[] tag = Cache.WrittenSpan.Slice(_headerLength, TagLength).ToArray();
            int len = Cache.WrittenSpan.GetLengthValue(_headerLength + TagLength, LengthType.Size(), _valueLengthIsLittleEndian);
            int dataLen = IsFullLength ? len - FrameExceptDataLength : len;

            if (dataLen < 0)
            {
                Cache.Remove(Headers.Length);
                return;
            }

            while (index >= 0)
            {
                if (MinFrameLength > 0 && dataLen < MinFrameLength)
                {
                    DecoderError("The data length is less than the minimum length", Cache.WrittenSpan);
                    Reset();
                    break;
                }

                if (MaxFrameLength > 0 && MaxFrameLength > MinFrameLength && dataLen > MaxFrameLength)
                {
                    DecoderError("The data length is greater than the maximum length.", Cache.WrittenSpan);
                    Reset();
                    break;
                }

                int allLen = dataLen + FrameExceptDataLength;
                if (allLen <= Cache.WrittenCount)
                {
                    if (Cache.WrittenSpan.Slice(_headerLength + TagLength + LengthType.Size() + dataLen, _tailLength).SequenceEqual(Tails.AsSpan()))
                    {
                        LastDecodedTime = DateTime.Now;
                        ByteFrameEventArgs e = new(this, ByteFrameParams.TagLengthParams(tag, len),
                            _headerLength + TagLength + LengthType.Size(), dataLen, GetResult(0, allLen));
                        await InvokeOnDecoderAsync(e);
                    }
                    else
                    {
                        DecoderError("Tail data verification failed.", Cache.WrittenSpan);
                        Reset();
                        break;
                    }
                }
                else
                {
                    break;
                }

                Cache.Remove(allLen);
                if (Cache.WrittenCount < FrameExceptDataLength) break;

                index = Cache.WrittenSpan.IndexOf(Headers);
                if (index > 0) Cache.Remove(index);
                if (Cache.WrittenCount < FrameExceptDataLength) break;

                tag = Cache.WrittenSpan.Slice(_headerLength, TagLength).ToArray();
                len = Cache.WrittenSpan.GetLengthValue(_headerLength + TagLength, LengthType.Size(), _valueLengthIsLittleEndian);
                dataLen = IsFullLength ? len - FrameExceptDataLength : len;
            }
        }

        /// <summary>
        /// 准备数据解码
        /// </summary>
        /// <param name="data">输入数据</param>
        /// <returns>判断此数据是否可以解码</returns>
        protected override bool PrepareDecode(ReadOnlySpan<byte> data)
        {
            if (data.Length == 0)
            {
                DecoderError("The data to be decoded is null.", data);
                return false;
            }

            if (Cache.WrittenCount > 0)
            {
                return true;
            }

            if (data.IndexOf(Headers.AsSpan()) < 0)
            {
                DecoderError("Data header verification failed.", data);
                return false;
            }

            return true;
        }

        /// <summary>
        /// 根据数据创建完整帧数据
        /// </summary>
        /// <param name="tags">标签</param>
        /// <param name="data">数据</param>
        /// <returns>完整帧数据</returns>
        public byte[] CreateFrame(byte[] tags, ReadOnlySpan<byte> data)
        {
            byte[] frame = new byte[FrameExceptDataLength + data.Length];
            Headers.AsSpan().CopyTo(frame.AsSpan());
            tags.AsSpan().CopyTo(frame.AsSpan().Slice(_headerLength));
            switch (LengthType)
            {
                case LengthType.Byte:
                    DecoderHelper.TryWriteByte(frame, Headers.Length + tags.Length, (byte)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length));
                    break;
                case LengthType.UShort:
                    DecoderHelper.TryWriteUShort(frame, Headers.Length + tags.Length, (ushort)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length), _valueLengthIsLittleEndian);
                    break;
                case LengthType.UInt:
                    DecoderHelper.TryWriteUInt(frame, Headers.Length + tags.Length, (uint)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length), _valueLengthIsLittleEndian);
                    break;
            }

            data.CopyTo(frame.AsSpan().Slice(Headers.Length + tags.Length + LengthType.Size()));
            Tails.AsSpan().CopyTo(frame.AsSpan().Slice(Headers.Length + tags.Length + LengthType.Size() + data.Length));
            return frame;
        }

        /// <summary>
        /// 解析完整帧数据
        /// </summary>
        /// <param name="frame">完整帧数据</param>
        /// <returns>解析是否成功</returns>
        public override ByteFrameEventArgs DecodeFrame(ReadOnlySpan<byte> frame)
        {
            if (frame.Length >= FrameExceptDataLength && frame.IndexOf(Headers) == 0 && frame.Slice(frame.Length - _tailLength, _tailLength).SequenceEqual(Tails.AsSpan()))
            {
                byte[] tag = frame.Slice(_headerLength, TagLength).ToArray();
                int len = frame.Slice(_headerLength + TagLength, LengthType.Size()).GetLengthValue(_valueLengthIsLittleEndian);
                int frameLen = IsFullLength ? len : len + FrameExceptDataLength;
                if (frameLen == frame.Length)
                {
                    return new(this, ByteFrameParams.TagLengthParams(tag, len),
                        _headerLength + TagLength + LengthType.Size(), frame.Length - FrameExceptDataLength, frame.ToArray());
                }
            }

            return new ByteFrameEventArgs(null, ByteFrameParams.Empty, frame.ToArray());
        }
    }
}
